#include "Range.h"

#include <string.h>
#include <fan-llvm/core/Core.h>
#include "FanInt.h"
#include "Func.h"
#include "IndexErr.h"
#include "List.h"
#include "ParseErr.h"
#include "Str.h"
#include "Type.h"


Type* Range_classType = NULL;
VTable* Range_classVTable = NULL;


/**********************************************
 *
 * Methods and fields
 *
 **********************************************/


/* Void makeInclusive(Int start, Int end) */
void Range_makeInclusive(Range* self, Int start, Int end)
{
  Obj_make(VAL_MAKE_REF(self));
  self->start = start;
  self->end = end;
  self->exclusive = FALSE;
}


/* Void makeExclusive(Int start, Int end) */
void Range_makeExclusive(Range* self, Int start, Int end)
{
  Obj_make(VAL_MAKE_REF(self));
  self->start = start;
  self->end = end;
  self->exclusive = TRUE;
}


/* Void make(Int start, Int end, Bool exclusive) */
void Range_make(Range* self, Int start, Int end, Bool exclusive)
{
  Obj_make(VAL_MAKE_REF(self));
  self->start = start;
  self->end = end;
  self->exclusive = exclusive;
}


/* Bool equals(Obj? obj) */
Bool Range_equals(Obj self, NullableObj obj)
{
  if (!VAL_IS_NULL(obj) && Obj_typeof(obj) == Range_classType)
  {
    Range* range1 = VAL_GET_REF(self);
    Range* range2 = VAL_GET_REF(obj);
    return ((range1->start == range2->start) && (range1->end == range2->end) && (range1->exclusive == range2->exclusive));
  }
  return FALSE;
}


/* Int hash() */
Int Range_hash(Obj self)
{
  Range* range = VAL_GET_REF(self);
#ifdef INT_IS_64BIT
  return ((range->start << 24) ^ range->end);
#else
  return ((range->start << 16) ^ range->end);
#endif
}


/* Str toStr() */
Str* Range_toStr(Obj self)
{
  Range* range = VAL_GET_REF(self);
  if (range->exclusive)
    return Str_newWithFormat("%i..<%i", range->start, range->end);
  else
    return Str_newWithFormat("%i..%i", range->start, range->end);
}


/* Range? fromStr(Str s, Bool checked) */
Range* Range_fromStr_1(Str* s)
{
  return Range_fromStr(s, TRUE);
}


/* Range? fromStr(Str s, Bool checked) */
Range* Range_fromStr(Str* s, Bool checked)
{
  Int len = Str_size(s);
  NullableInt dot = Str_indexChar(s, '.');
  if (!VAL_IS_NULL(dot))
  {
    Int dotPos = VAL_GET_INT(dot);
    if (dotPos < (len - 2) && Str_get(s, dotPos + 1) == '.')
    {
      Bool exclusive = (Str_get(s, dotPos + 2) == '<');
      Int endPos = dotPos + (exclusive ? 3 : 2);
      NullableInt start = Int_fromStr(Str_substr(s, 0, dotPos), 10, FALSE);
      NullableInt end = Int_fromStr(Str_substr(s, endPos, len), 10, FALSE);
      if (!VAL_IS_NULL(start) && !VAL_IS_NULL(end))
        return Range_new(VAL_GET_INT(start), VAL_GET_INT(end), exclusive);
    }
  }

  if (checked)
    exceptionThrow(ParseErr_newWithFormattedMsg("Invalid Range: '%r'", s));

  return NULL;
}


/* Int start() */
Int Range_start(Range* self)
{
  return self->start;
}


/* Int end() */
Int Range_end(Range* self)
{
  return self->end;
}


/* Bool inclusive() */
Bool Range_inclusive(Range* self)
{
  return !self->exclusive;
}


/* Bool exclusive() */
Bool Range_exclusive(Range* self)
{
  return self->exclusive;
}


/* Bool isEmpty() */
Bool Range_isEmpty(Range* self)
{
  return (self->exclusive && self->start == self->end);
}


/* Int? min() */
NullableInt Range_min(Range* self)
{
  if (Range_isEmpty(self))
    return VAL_NULL;

  if (self->start <= self->end)
    return VAL_MAKE_INT(self->start);
  else
    return VAL_MAKE_INT(self->exclusive ? (self->end + 1) : self->end);
}


/* Int? max() */
NullableInt Range_max(Range* self)
{
  if (Range_isEmpty(self))
    return VAL_NULL;

  if (self->start <= self->end)
    return VAL_MAKE_INT(self->exclusive ? (self->end - 1) : self->end);
  else
    return VAL_MAKE_INT(self->start);
}


/* Int? first() */
NullableInt Range_first(Range* self)
{
  if (Range_isEmpty(self))
    return VAL_NULL;
  return VAL_MAKE_INT(self->start);
}


/* Int? last() */
NullableInt Range_last(Range* self)
{
  if (!self->exclusive)
    return VAL_MAKE_INT(self->end);
  else
  {
    if (self->start <= self->end)
      return VAL_MAKE_INT(self->end - 1);
    else
      return VAL_MAKE_INT(self->end + 1);
  }
}


/* Bool contains(Int i) */
Bool Range_contains(Range* self, Int i)
{
  if (self->start <= self->end)
  {
    if (self->exclusive)
      return (self->start <= i && i < self->end);
    else
      return (self->start <= i && i <= self->end);
  }
  else
  {
    if (self->exclusive)
      return (self->end < i && i <= self->start);
    else
      return (self->end <= i && i <= self->start);
  }
}


/* Range offset(Int offset) */
Range* Range_offset(Range* self, Int offset)
{
  if (offset == 0)
    return self;
  else
    return Range_new(self->start + offset, self->end + offset, self->exclusive);
}


/* Void each(|Int->Void| c) */
void Range_each(Range* self, Func* c)
{
  Int start = self->start;
  Int end = self->end;

  if (start <= end)
  {
    if (self->exclusive)
      end--;
    for (Int i = start; i <= end; i++)
      FUNC_VTABLE(c)->call_1(c, VAL_MAKE_INT(i));
  }
  else
  {
    if (self->exclusive)
      end++;
    for (Int i = start; i >= end; i--)
      FUNC_VTABLE(c)->call_1(c, VAL_MAKE_INT(i));
  }
}


/* Obj?[] map(|Int->Obj?| c) */
List* Range_map(Range* self, Func* c)
{
  List* list;
  Int start = self->start;
  Int end = self->end;
  if (start < end)
  {
    if (self->exclusive)
      end--;
    list = List_new(Func_returns(c), end - start + 1);
    for (Int i = start; i <= end; i++)
      List_add(list, FUNC_VTABLE(c)->call_1(c, VAL_MAKE_INT(i)));
  }
  else
  {
    if (self->exclusive)
      end++;
    list = List_new(Func_returns(c), start - end + 1);
    for (Int i = start; i >= end; i--)
      List_add(list, FUNC_VTABLE(c)->call_1(c, VAL_MAKE_INT(i)));
  }
  return list;
}


/* Int[] toList() */
List* Range_toList(Range* self)
{
  List* list;
  Int start = self->start;
  Int end = self->end;
  if (start < end)
  {
    if (self->exclusive)
      end--;
    list = List_new(Obj_classType, end - start + 1);
    for (Int i = start; i <= end; i++)
      List_add(list, VAL_MAKE_INT(i));
  }
  else
  {
    if (self->exclusive)
      end++;
    list = List_new(Obj_classType, start - end + 1);
    for (Int i = start; i >= end; i--)
      List_add(list, VAL_MAKE_INT(i));
  }
  return list;
}


/* Int random() */
Int Range_random(Range* self)
{
  return Int_random(self);
}


/**********************************************
 *
 * Extra methods
 *
 **********************************************/


/* Create a new Range instance and initialize it with Range_make() */
Range* Range_new(Int start, Int end, Bool exclusive)
{
  Range* range = (Range*)Obj_allocate(sizeof(Range), Range_classVTable);
  Range_make(range, start, end, exclusive);
  return range;
}


/* Get the absolute index where to start when slicing a collection of the given size.
 * Throw IndexErr if the range's start-index is not in the collection */
Int Range_absStart(Range* self, Int size)
{
  Int absStart = self->start;
  if (absStart < 0)
    absStart += size;
  if (absStart < 0 || absStart >= size)
    exceptionThrow(IndexErr_newWithRange(self));
  return absStart;
}


/* Get the absolute index where to end when slicing a collection of the given size.
 * Throw IndexErr if the range's end-index is not in the collection */
Int Range_absEnd(Range* self, Int size)
{
  Int absEnd = self->end;
  if (absEnd < 0)
    absEnd += size;
  if (self->exclusive)
    absEnd--;
  if (absEnd < 0 || absEnd >= size)
    exceptionThrow(IndexErr_newWithRange(self));
  return absEnd;
}


/**********************************************
 *
 * Type-system functions
 *
 **********************************************/


Type* Range_initType(void)
{
  if (Range_classType == NULL)
  {
    Type* baseType = Obj_initType();
    VTable* vtable = Range_initVTable();
    Type* type = Type_new("Range", baseType, vtable, sizeof(Range), sizeof(RangeVTable));
    Type_registerCtorImplementation(type, "make", Range_make, 3);
    Type_registerCtorImplementation(type, "makeExclusive", Range_makeExclusive, 2);
    Type_registerCtorImplementation(type, "makeInclusive", Range_makeInclusive, 2);
    Type_registerMethodImplementation(type, "contains", Range_contains, 1);
    Type_registerMethodImplementation(type, "each", Range_each, 1);
    Type_registerMethodImplementation(type, "end", Range_end, 0);
    Type_registerMethodImplementation(type, "equals", Range_equals, 1);
    Type_registerMethodImplementation(type, "exclusive", Range_exclusive, 0);
    Type_registerMethodImplementation(type, "first", Range_first, 0);
    Type_registerMethodImplementation(type, "fromStr", Range_fromStr_1, 1);
    Type_registerMethodImplementation(type, "fromStr", Range_fromStr, 2);
    Type_registerMethodImplementation(type, "hash", Range_hash, 0);
    Type_registerMethodImplementation(type, "inclusive", Range_inclusive, 0);
    Type_registerMethodImplementation(type, "isEmpty", Range_isEmpty, 0);
    Type_registerMethodImplementation(type, "last", Range_last, 0);
    Type_registerMethodImplementation(type, "map", Range_map, 1);
    Type_registerMethodImplementation(type, "max", Range_max, 0);
    Type_registerMethodImplementation(type, "min", Range_min, 0);
    Type_registerMethodImplementation(type, "offset", Range_offset, 1);
    Type_registerMethodImplementation(type, "random", Range_random, 0);
    Type_registerMethodImplementation(type, "start", Range_start, 0);
    Type_registerMethodImplementation(type, "toList", Range_toList, 0);
    Type_registerMethodImplementation(type, "toStr", Range_toStr, 0);
    Range_classType = type;
    Range_initStatic();
  }
  return Range_classType;
}


VTable* Range_initVTable(void)
{
  if (Range_classVTable == NULL)
  {
    VTable* vtable = memoryAlloc(sizeof(RangeVTable));
    memcpy(vtable, Obj_initVTable(), sizeof(FanObjVTable));
    ((FanObjVTable*)vtable)->equals = Range_equals;
    ((FanObjVTable*)vtable)->hash = Range_hash;
    ((FanObjVTable*)vtable)->toStr = Range_toStr;
    Range_classVTable = vtable;
  }
  return Range_classVTable;
}


void Range_initStatic(void)
{
}
